package bubbleindex;

import com.nativelibs4java.opencl.CLBuffer;
import com.nativelibs4java.opencl.CLContext;
import com.nativelibs4java.opencl.CLEvent;
import com.nativelibs4java.opencl.CLKernel;
import com.nativelibs4java.opencl.CLMem;
import com.nativelibs4java.opencl.CLQueue;
import java.nio.ByteOrder;
import java.util.concurrent.Callable;
import org.bridj.Pointer;
import static org.bridj.Pointer.allocateFloats;

/**
 *
 * @author ttrott
 */
public class MyCallable implements Callable<Float> {
     
    private final int NumberOfDays;
    private final int j;
    private final CLQueue clqueue;
    private final CLContext clcontext;
    private final CLKernel claddFloatsKernel;
    static final int Q_SIZE = 18;
    static final int H_SIZE = 19;
    static float[] Q = new float [Q_SIZE];
    static float[] H = new float [H_SIZE];
    
    public MyCallable (CLContext context, CLKernel addFloatsKernel, CLQueue queue, 
            int index, final int number) {
        NumberOfDays = number;
        j = index;
        clqueue = queue;
        clcontext = context;
        claddFloatsKernel = addFloatsKernel;
    }
    
    /**
     * This is a callable task to calculate the HQ derivative value of a 
     * given time series.
     * @return The HQ Derivative value, or The Value of the Bubble Index 
     */
    
    @Override
    public Float call() {
                
        if (!BuildIndex.Stop) {
            
            int FREQ_SIZE = 70;

            ByteOrder byteOrder = clcontext.getByteOrder();

            Pointer<Float> Values = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            Pointer<Float> MeanArray = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            Pointer<Float> timevalues = allocateFloats(NumberOfDays).order(byteOrder);
            Pointer<Float> logtimeValues = allocateFloats(NumberOfDays).order(byteOrder);
            Pointer<Float> testfreq = allocateFloats(FREQ_SIZE).order(byteOrder);
            Pointer<Float> q = allocateFloats(Q_SIZE).order(byteOrder);
            Pointer<Float> h = allocateFloats(H_SIZE).order(byteOrder);
            Pointer<Float> hqderiv = allocateFloats(NumberOfDays * (Q_SIZE * H_SIZE + 1)).order(byteOrder);

            //Initialize important values
            float Omega_float = BubbleIndex.Omega;
            float M_float = BubbleIndex.M;
            float StartingPoint = (Omega_float / (2.0f * 3.14159f) - 0.2f);
            float Increments = 0.01f;

            //Initialize important arrays
            for (int i = 0; i < FREQ_SIZE; i++) {
                testfreq.set(i, (float) StartingPoint + i * Increments);
            }

            StartingPoint = 0.1f;
            Increments = 0.05f;
            for (int i = 0; i < Q_SIZE; i++) {
                q.set(i, (float) StartingPoint + i * Increments);
                Q[i] = (float) StartingPoint + i * Increments;
            }

            StartingPoint = -1.0f * 0.9f;
            Increments = 0.1f;
            for (int i = 0; i < H_SIZE; i++) {
                h.set(i, (float) StartingPoint + i * Increments);
                H[i] = (float) StartingPoint + i * Increments;                
            }        

            double[] TimeValues = new double[NumberOfDays];
            double[] TimeValues_M_Power = new double[NumberOfDays];
            double[] LogCosTimeValues = new double[NumberOfDays];
            double[] SelectedData = new double[NumberOfDays];
            double[] Coef = new double[3];
            float[] LogTimeValuesTemp = new float[NumberOfDays];

            //Arrays used in the H,Q derivative calculation and periodogram
            for (int k = 0; k < NumberOfDays; k++) {
                TimeValues[k] = NumberOfDays + BubbleIndex.T_crit - k;
                TimeValues_M_Power[k] = Math.pow(TimeValues[k], 
                        M_float);
                LogCosTimeValues[k] = Math.cos(Omega_float * 
                    Math.log(TimeValues[k])) * TimeValues_M_Power[k];
                SelectedData[k] = BubbleIndex.DailyPriceValues[k + j + 1];
                LogTimeValuesTemp[k] = (float) Math.log(TimeValues[k]);
            }
              
            //Normalize data to a price starting at 100
            BuildIndex.Normalize(SelectedData, NumberOfDays);

            BuildIndex.DataReverse(SelectedData, NumberOfDays);

            //Fit the curve with the equation given in:
            BuildIndex.LinearFit(SelectedData, TimeValues_M_Power, LogCosTimeValues,
                        Coef, NumberOfDays);
            
            float[] mean = new float[Q_SIZE * H_SIZE];
            
            for (int i = 0; i < Q_SIZE; i++) {
                
                float logQi = (float) Math.log(Q[i]) * BubbleIndex.Omega;
                float QiM = (float) Math.pow(Q[i],BubbleIndex.M);
                
                for (int p = 0; p < H_SIZE; p++) {
                    
                    float powTempVar = (float) Math.pow((1.0f - Q[i]), H[p]);
                    float C_one, C_two, B_prime, C_prime;

                    C_one = 1.0f - QiM * (float) Math.cos(logQi);

                    C_two = (float) Math.sin(logQi);
                    //Took out the negative sign for B_Prime
                    B_prime = 1.0f * ((float) Coef[1]) * (1.0f - QiM) * 1.0f / powTempVar;

                    C_prime = ((float) Coef[2]) * 1.0f / powTempVar;
                    
                    float sum = 0.0f;
                    
                    for (int k = 0; k < NumberOfDays; k++) {

                        float G_function = C_one * ((float) Math.cos(BubbleIndex.Omega *
                                LogTimeValuesTemp[k])) + C_two * ((float) Math.sin(
                                BubbleIndex.Omega * LogTimeValuesTemp[k]));
                        
                        float tempValue = ((float) Math.pow(TimeValues[k], 
                                BubbleIndex.M - H[p])) *
                                (B_prime + C_prime * G_function);
                        sum = sum + tempValue;
                        hqderiv.set((i + p * Q_SIZE) * NumberOfDays + k, tempValue);
                    }
                    
                    mean[i + p * Q_SIZE] = sum * 1.0f / NumberOfDays;
                }
            }
            
            //Prepare the arrays to be copied to device memory
            for (int i = 0; i < NumberOfDays; i++) {
                timevalues.set(i, (float)TimeValues[i]);
                logtimeValues.set(i, LogTimeValuesTemp[i]);
            }
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                Values.set(i, 0.0f);
                MeanArray.set(i, mean[i]);
            }       

            // Create OpenCL input buffers (using the native memory pointers) :
            CLBuffer<Float> 
                values = clcontext.createFloatBuffer(CLMem.Usage.Output, Values),
                meanArray = clcontext.createFloatBuffer(CLMem.Usage.Input, MeanArray),
                LogTimeValues = clcontext.createFloatBuffer(CLMem.Usage.Input, logtimeValues),
                TimeValuesFinal = clcontext.createFloatBuffer(CLMem.Usage.Input, timevalues),                
                TestFrequencies = clcontext.createFloatBuffer(CLMem.Usage.Input, testfreq),
                Q2 = clcontext.createFloatBuffer(CLMem.Usage.Input, q),
                H2 = clcontext.createFloatBuffer(CLMem.Usage.Input, h),
                HQDerivativeData = clcontext.createFloatBuffer(CLMem.Usage.Input, hqderiv);

            Values.release();
            MeanArray.release();
            logtimeValues.release();
            timevalues.release();
            testfreq.release();
            q.release();
            h.release();
            hqderiv.release();

            float Temp = 0.0f;
            final float output;

            Pointer<Float> outPtr = allocateFloats(Q_SIZE * H_SIZE).order(byteOrder);
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                outPtr.set(i, 0.0f);
            }
            
            CLEvent addEvt;
            synchronized (claddFloatsKernel) {
                claddFloatsKernel.setArg(0, values);
                claddFloatsKernel.setArg(1, meanArray);
                claddFloatsKernel.setArg(2, LogTimeValues);
                claddFloatsKernel.setArg(3, TimeValuesFinal);
                claddFloatsKernel.setArg(4, TestFrequencies);
                claddFloatsKernel.setArg(5, HQDerivativeData);
                claddFloatsKernel.setArg(6, Q2);
                claddFloatsKernel.setArg(7, H2);
                claddFloatsKernel.setArg(8, M_float);
                claddFloatsKernel.setArg(9, Omega_float);
                claddFloatsKernel.setArg(10, (float)Coef[1]);
                claddFloatsKernel.setArg(11, (float)Coef[2]);
                claddFloatsKernel.setArg(12, Q_SIZE);
                claddFloatsKernel.setArg(13, H_SIZE);
                claddFloatsKernel.setArg(14, FREQ_SIZE);
                claddFloatsKernel.setArg(15, NumberOfDays);

	        int[] globalSizes = new int[] {Q_SIZE , H_SIZE};
	        addEvt = claddFloatsKernel.enqueueNDRange(clqueue, globalSizes);
            }
           
            outPtr = values.read(clqueue, addEvt); 

            values.release();
            meanArray.release();
            LogTimeValues.release();
            TimeValuesFinal.release();
            HQDerivativeData.release();
            TestFrequencies.release();
            Q2.release();
            H2.release();                
            
            for (int i = 0; i < Q_SIZE * H_SIZE; i++) {
                if (outPtr.get(i) > Temp) {
                    Temp = outPtr.get(i);
                }
            }
            
            outPtr.release();
            output = Temp;
            
            final int DisplayPeriod = j + NumberOfDays;
            final int extra = NumberOfDays;
            final String name = BubbleIndex.SelectionName;
            final String DisplayPeriodString = BubbleIndex.DailyPriceDateList.get(DisplayPeriod);
            BubbleIndex.displayOutput("Name: " + name + " Date: " + DisplayPeriodString +
                            " Value: " + output + " Length: " + extra, false);

            return Temp;                
        }
                
        else {            
            return 0.0f;        
        }
    }
}
  
   